package com.lanyan.web.controller;

import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.extension.conditions.update.LambdaUpdateChainWrapper;
import com.ijpay.core.IJPayHttpResponse;
import com.ijpay.core.enums.RequestMethodEnum;
import com.ijpay.core.enums.SignType;
import com.ijpay.core.enums.TradeType;
import com.ijpay.core.kit.HttpKit;
import com.ijpay.core.kit.IpKit;
import com.ijpay.core.kit.PayKit;
import com.ijpay.core.kit.WxPayKit;
import com.ijpay.wxpay.WxPayApi;
import com.ijpay.wxpay.WxPayApiConfig;
import com.ijpay.wxpay.WxPayApiConfigKit;
import com.ijpay.wxpay.enums.WxDomainEnum;
import com.ijpay.wxpay.enums.v3.TransferApiEnum;
import com.ijpay.wxpay.model.*;
import com.ijpay.wxpay.model.v3.BatchTransferModel;
import com.ijpay.wxpay.model.v3.TransferDetailInput;
import com.lanyan.common.core.domain.AjaxResult;
import com.lanyan.common.core.redis.RedisCache;
import com.lanyan.common.utils.Arith;
import com.lanyan.common.utils.SecurityUtils;
import com.lanyan.common.utils.StringUtils;
import com.lanyan.common.utils.http.HttpUtils;
import com.lanyan.common.utils.ip.IpUtils;
import com.lanyan.common.utils.wx.WxAppletUserInfo;
import com.lanyan.system.domain.*;
import com.lanyan.system.domain.vo.*;
import com.lanyan.system.service.*;
import com.lanyan.web.core.config.WxConfig;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import okhttp3.Response;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static javax.security.auth.callback.ConfirmationCallback.OK;

/**
 * <p>IJPay 让支付触手可及，封装了微信支付、支付宝支付、银联支付常用的支付方式以及各种常用的接口。</p>
 * <p>
 * <p>不依赖任何第三方 mvc 框架，仅仅作为工具使用简单快速完成支付模块的开发，可轻松嵌入到任何系统里。 </p>
 * <p>
 * <p>IJPay 交流群: 723992875</p>
 * <p>
 * <p>Node.js 版: https://gitee.com/javen205/TNWX</p>
 * <p>
 * <p>微信支付 Demo</p>
 *
 * @author Javen
 */
@RestController
@RequestMapping("wxPay")
@CrossOrigin(origins = "*", maxAge = 3600)
public class WxPayController {
    private final Logger log = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private Snowflake snowflake;
    @Autowired
    private ISysConfigService configService;

    @Autowired
    private IAccountService accountService;
    @Autowired
    private IOrdersService ordersService;
    @Autowired
    private IPayService payService;
    @Autowired
    private IVipSettingService vipSettingService;
    @Autowired
    private IBalanceHistoryService balanceHistoryService;
    @Autowired
    private IOrdersItemService ordersItemService;
    @Autowired
    private IProductService productService;
    @Autowired
    private ISkuService skuService;
    @Autowired
    private TransferToUser transferToUser;
    String serialNo;

    @GetMapping("/getKey")
    @ResponseBody
    public String getKey() {
        return WxPayApi.getSignKey(configService.selectConfigByKey("wx.machId"), configService.selectConfigByKey("wx.machSecret"), SignType.MD5);
    }

    /**
     * 微信小程序支付
     */
    @RequestMapping(value = "/miniAppPay", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    @ApiImplicitParams({
            @ApiImplicitParam(name = "ordersNo", value = "所属订单号,三选一", dataType = "Long", dataTypeClass = Long.class),
            @ApiImplicitParam(name = "vipSettingId", value = "所属会员,三选一", dataType = "Long", dataTypeClass = Long.class),
            @ApiImplicitParam(name = "recharge", value = "充值金额,三选一", dataType = "BigDecimal", dataTypeClass = BigDecimal.class),
            @ApiImplicitParam(name = "payType", value = "支付方式（0微信支付 1余额支付）", dataType = "String", dataTypeClass = String.class),
    })
    @ApiOperation("微信小程序支付")
    public AjaxResult miniAppPay(HttpServletRequest request, String ordersNo, Long vipSettingId,BigDecimal recharge,String payType) {
        String ip = IpUtils.getIpAddr(request);
        String payNo = String.valueOf(snowflake.nextId());
        BigDecimal money = BigDecimal.ZERO;
        Account account = accountService.getById(SecurityUtils.getUserId());
        String title = "";
        if (StringUtils.isNotEmpty(ordersNo)){
            payNo = "O" + payNo;
            if (ordersNo.startsWith("A")){
                List<Orders> orders = ordersService.lambdaQuery()
                        .eq(Orders::getDelFlag, "0")
                        .eq(Orders::getTotalNo, ordersNo)
                        .list();
                ordersService.lambdaUpdate()
                        .eq(Orders::getTotalNo,ordersNo)
                        .set(Orders::getPayNo,payNo)
                        .update();
                money = orders.stream().map(OrdersVo::getMoney).reduce(BigDecimal.ZERO,BigDecimal::add);
            }else {
                Orders orders = ordersService.lambdaQuery()
                        .eq(Orders::getDelFlag, "0")
                        .eq(OrdersVo::getOrdersNo, ordersNo)
                        .one();
                ordersService.lambdaUpdate()
                        .eq(Orders::getOrdersId,orders.getOrdersId())
                        .set(Orders::getPayNo,payNo)
                        .update();
                money = orders.getMoney();
            }
            title = "购买商品";
        }else {
            Pay pay = new Pay();
            if (vipSettingId != null){
                VipSetting vipSetting = vipSettingService.getById(vipSettingId);
                payNo = "V" + payNo;
                money = vipSetting.getPrice();
                pay.setVipId(vipSettingId);
                title = "充值会员";
            }else {
                payNo = "R" + payNo;
                money = recharge;
            }
            pay.setPayNo(payNo);
            pay.setAccountId(account.getAccountId());
            pay.setMoney(money);
            pay.setIsPay("0");
            payService.save(pay);
        }

        if (StringUtils.equals(payType,"1")){
            if (account.getBalance().compareTo(money) < 0 ){
                return AjaxResult.error("余额不足");
            }
            pp(payNo);
            account.setBalance(account.getBalance().subtract(money));
            accountService.updateById(account);
            BalanceHistory balanceHistory = new BalanceHistory();
            balanceHistory.setType("0");
            balanceHistory.setAccountId(account.getAccountId());
            balanceHistory.setMoney(BigDecimal.ZERO.subtract(money));
            balanceHistory.setTitle(title);
            balanceHistory.setMessage(title);
            balanceHistoryService.save(balanceHistory);
            return AjaxResult.success();
        }

        //todo 删除下列代码
//        pp(payNo);

        Map<String, String> params = UnifiedOrderModel
                .builder()
                .appid(configService.selectConfigByKey("wx.appId"))
                .mch_id(configService.selectConfigByKey("wx.machId"))
                .nonce_str(WxPayKit.generateStr())
                .body("支付商品")
                .attach("支付成功")
                .out_trade_no(payNo)
                .total_fee(String.valueOf((int) Arith.mul(money.doubleValue(), 100)))
                .spbill_create_ip(ip)
                .notify_url(configService.selectConfigByKey("wx.domainName") + "/wxPay/payNotify")
                .trade_type(TradeType.JSAPI.getTradeType())
                .openid(account.getOpenId())
                .build()
                .createSign(configService.selectConfigByKey("wx.machSecret"), SignType.HMACSHA256);
        System.out.println(WxPayKit.toXml(params));
        String xmlResult = WxPayApi.pushOrder(false, params);

        log.info(xmlResult);
        Map<String, String> result = WxPayKit.xmlToMap(xmlResult);

        String returnCode = result.get("return_code");
        String returnMsg = result.get("return_msg");
        if (!WxPayKit.codeIsOk(returnCode)) {
            return AjaxResult.error(201,returnMsg);
        }
        String resultCode = result.get("result_code");
        if (!WxPayKit.codeIsOk(resultCode)) {
            return AjaxResult.error(201,returnMsg);
        }
        // 以下字段在 return_code 和 result_code 都为 SUCCESS 的时候有返回
        String prepayId = result.get("prepay_id");
        Map<String, String> packageParams = WxPayKit.miniAppPrepayIdCreateSign(configService.selectConfigByKey("wx.appId"), prepayId,
                configService.selectConfigByKey("wx.machSecret"), SignType.HMACSHA256);
        packageParams.put("payNo",payNo);
        System.out.println(packageParams);
        return AjaxResult.success(packageParams);
    }

    /**
     * 获取RSA加密公钥
     */
    @RequestMapping(value = "/getPublicKey", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public String getPublicKey() {
        try {
            WxPayApiConfig wxPayApiConfig = WxPayApiConfigKit.getWxPayApiConfig();
            Map<String, String> params = new HashMap<>(4);
            params.put("mch_id", wxPayApiConfig.getMchId());
            params.put("nonce_str", String.valueOf(System.currentTimeMillis()));
            params.put("sign_type", "MD5");
            String createSign = WxPayKit.createSign(params, wxPayApiConfig.getPartnerKey(), SignType.MD5);
            params.put("sign", createSign);
            return WxPayApi.getPublicKey(params, wxPayApiConfig.getCertPath(), wxPayApiConfig.getMchId());
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    /**
     * 退款通知
     * 退款回调
     */
    @RequestMapping(value = "/refundNotify", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public String refundNotify(HttpServletRequest request) {
        String xmlMsg = HttpKit.readData(request);
        log.info("退款通知=" + xmlMsg);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);

        String returnCode = params.get("return_code");
        // 注意重复通知的情况，同一订单号可能收到多次通知，请注意一定先判断订单状态
        if (WxPayKit.codeIsOk(returnCode)) {
            String reqInfo = params.get("req_info");
            String decryptData = WxPayKit.decryptData(reqInfo, WxPayApiConfigKit.getWxPayApiConfig().getPartnerKey());
            log.info("退款通知解密后的数据=" + decryptData);
            // 更新订单信息
            // 发送通知等
            Map<String, String> xml = new HashMap<String, String>(2);
            xml.put("return_code", "SUCCESS");
            xml.put("return_msg", "OK");
            return WxPayKit.toXml(xml);
        }
        return null;
    }

    /**
     * 异步通知
     * 获取回调
     */
    @RequestMapping(value = "/payNotify", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public AjaxResult payNotify(HttpServletRequest request) throws Exception {
        String xmlMsg = HttpKit.readData(request);
        Map<String, String> params = WxPayKit.xmlToMap(xmlMsg);
        String outTradeNo = params.get("out_trade_no");
//        String outTradeNo = "985e5f1a0382df63116959e5431627cc";

        // 注意重复通知的情况，同一订单号可能收到多次通知，请注意一定先判断订单状态
        // 注意此处签名方式需与统一下单的签名类型一致

        pp(outTradeNo);

        //订单已经被修改

        return AjaxResult.success();
    }

    @GetMapping(value = "pp")
    @ApiOperation("模拟支付")
    public void pp(String outTradeNo){
        if (outTradeNo.startsWith("O")){
            List<Orders> ordersList = ordersService.lambdaQuery()
                    .eq(Orders::getPayNo, outTradeNo)
                    .eq(OrdersVo::getIsPay,"0")
                    .list();

            if (ordersList.size() != 0){
                for (Orders orders : ordersList) {
                    LambdaUpdateChainWrapper<Orders> wrapper = ordersService.lambdaUpdate()
                            .eq(Orders::getPayNo, orders.getPayNo())
                            .set(OrdersVo::getIsPay, "1");
                    if (StringUtils.equals(orders.getState(),"0")){
                        wrapper.set(OrdersVo::getState,"1");
                        ordersService.sendWx(orders.getOrdersId());
                    }
                    wrapper.update();
                    Account account = accountService.getById(orders.getAccountId());
                    WxAppletUserInfo.test01(
                            account.getOpenId(),
                            "支付商品",
                            outTradeNo,
                            configService.selectConfigByKey("wx.machId"),
                            configService.selectConfigByKey("wx.appId"),
                            configService.selectConfigByKey("wx.secret"),
                            null);

                    List<OrdersItem> ordersItemList = ordersItemService.lambdaQuery()
                            .eq(OrdersItem::getDelFlag, "0")
                            .eq(OrdersItem::getOrdersId, orders.getOrdersId()).list();
                    for (OrdersItem item : ordersItemList) {
                        Product product = productService.getById(item.getProductId());
                        productService.lambdaUpdate()
                                .eq(ProductVo::getProductId,product.getProductId())
                                .set(ProductVo::getStock,product.getSales()+item.getNum())
                                .update();
                    }
                }
            }
        }else{
            Pay pay = payService.lambdaQuery()
                    .eq(PayVo::getPayNo, outTradeNo)
                    .one();
            Account account = accountService.getById(pay.getAccountId());
            if (StringUtils.equals(pay.getIsPay(),"0")){
                payService.lambdaUpdate()
                        .eq(PayVo::getPayId,pay.getPayId())
                        .set(PayVo::getIsPay,"1")
                        .update();
                if (outTradeNo.startsWith("V")){
                    VipSetting vipSetting = vipSettingService.getById(pay.getVipId());
                    Date now = new Date();
                    long timer = vipSetting.getTimer() * 30 * 24 * 60 * 60 * 1000L;
                    if (account.getVipEnd() != null && account.getVipEnd().after(now)){
                        now.setTime(account.getVipEnd().getTime() + timer);
                    }else {
                        now.setTime(now.getTime() + timer);
                    }
                    accountService.lambdaUpdate()
                            .eq(AccountVo::getAccountId,account.getAccountId())
                            .set(AccountVo::getVipEnd,now)
                            .update();
                }else {
                    accountService.lambdaUpdate()
                            .eq(AccountVo::getAccountId,account.getAccountId())
                            .set(AccountVo::getBalance,account.getBalance().add(pay.getMoney()))
                            .update();
                    BalanceHistory balanceHistory = new BalanceHistory();
                    balanceHistory.setAccountId(account.getAccountId());
                    balanceHistory.setMessage("充值余额");
                    balanceHistory.setTitle("充值余额");
                    balanceHistory.setMoney(pay.getMoney());
                    balanceHistory.setType("0");
                    balanceHistory.setPointTo(pay.getPayId().toString());
                    balanceHistoryService.save(balanceHistory);
                }

                WxAppletUserInfo.test01(
                        account.getOpenId(),
                        "支付商品",
                        outTradeNo,
                        configService.selectConfigByKey("wx.machId"),
                        configService.selectConfigByKey("wx.appId"),
                        configService.selectConfigByKey("wx.secret"),
                        null);
            }
        }
    }

    /**
     * 微信退款
     */
    @RequestMapping(value = "/refund", method = {RequestMethod.POST, RequestMethod.GET})
    @ResponseBody
    public AjaxResult refund(@RequestParam("out_trade_no") String outTradeNo,
                         @RequestParam("total_fee") BigDecimal totalFee,
                         @RequestParam("refund_fee") BigDecimal refundFee) {


        String tf = totalFee.multiply(totalFee).toString();
        tf = tf.substring(0,tf.length()-3);
        String rf = refundFee.multiply(refundFee).toString();
        rf = rf.substring(0,rf.length()-3);

        Map<String, String> params = RefundModel.builder()
                .appid(configService.selectConfigByKey("wx.appId"))
                .mch_id(configService.selectConfigByKey("wx.machId"))
                .nonce_str(WxPayKit.generateStr())
//                .transaction_id(id)  //商户号订单号
                .out_trade_no(outTradeNo)
                .out_refund_no(String.valueOf(snowflake.nextId()))
                .total_fee(tf)
                .refund_fee(rf)
                .notify_url(configService.selectConfigByKey("wx.domainName") + "/wxPay/refundNotify")
                .build()
                .createSign(configService.selectConfigByKey("wx.machSecret"), SignType.MD5);
//        String data = WxPayApi.orderRefund(false, params, wxConfig.getCertPath(), wxConfig.getMachId());
        String xmlResult = WxPayApi.orderRefundByProtocol(false, params, FileUtil.getInputStream(
                        configService.selectConfigByKey("wx.certPath")),
                configService.selectConfigByKey("wx.machId"),"");
        System.out.println(xmlResult);
        return AjaxResult.success(xmlResult);
    }

    public void sendWxP(Long accountId,String payNo){
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ");
        Account account = accountService.getById(accountId);
        String mpToken = WxAppletUserInfo.getMPToken(
                configService.selectConfigByKey("wx.appId"),
                configService.selectConfigByKey("wx.secret")
        );
        JSONObject params = new JSONObject();
//        params.put("access_token",mpToken);//接口调用凭证，该参数为 URL 参数，非 Body 参数。使用getAccessToken或者authorizer_access_token
        JSONObject orderKey = new JSONObject();
        orderKey.put("order_number_type",1);//订单单号类型，用于确认需要上传详情的订单。枚举值1，使用下单商户号和商户侧单号；枚举值2，使用微信支付单号。
        orderKey.put("out_trade_no",payNo);
        orderKey.put("mchid",configService.selectConfigByKey("wx.machId"));
        params.put("order_key",orderKey);
        params.put("logistics_type",3);//物流模式，发货方式枚举值：1、实体物流配送采用快递公司进行实体物流配送形式 2、同城配送 3、虚拟商品，虚拟商品，例如话费充值，点卡等，无实体配送形式 4、用户自提
        params.put("delivery_mode",1);//发货模式，发货模式枚举值：1、UNIFIED_DELIVERY（统一发货）2、SPLIT_DELIVERY（分拆发货） 示例值: UNIFIED_DELIVERY
        JSONArray shippingList = new JSONArray();
        JSONObject shippingListItem = new JSONObject();
        shippingListItem.put("item_desc","购买商品");//商品信息，例如：微信红包抱枕*1个，限120个字以内
        shippingList.add(shippingListItem);
        params.put("shipping_list",shippingList);
        params.put("upload_time",sdf.format(new Date()));//上传时间，用于标识请求的先后顺序 示例值: `2022-12-15T13:29:35.120+08:00`
        JSONObject payer = new JSONObject();
        payer.put("openid",account.getOpenId());
        params.put("payer",payer);
        System.out.println(params.toString());
        HttpUtils.sendPost("https://api.weixin.qq.com/wxa/sec/order/upload_shipping_info?access_token="+mpToken,params.toJSONString());
    }

}
